/*
   Zii EGGxplorer - A simple Zii file browser

   Copyright (C) 2009 - 2010 Claudio Corti

   Implementation file for PathInfo class: returns informations about a path and is the path is a
   directory, it allows browsing of the content.
*/

// Unix standard headers
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// C++ standard library
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <climits>

// Application headers
#include "PathInfo.h"

typedef struct stat sys_stat;

// Constructor
PathInfo::PathInfo(char *FullPath) : full_path(NULL), parent_path(NULL), linked_path(NULL),
   error_str(NULL), directory_stream(NULL)
{
   path_len = strlen(FullPath);
   // strip final '/' from full_path, unless FullPath is "/"
   if (path_len > 1 && FullPath[-1] == '/') --path_len;
   full_path = new char[path_len + 1];
   strncpy(full_path, FullPath, path_len);
   full_path[path_len] = '\0';

   char *base_name = strrchr(full_path, '/');

   if (base_name != NULL) base_name_position = (base_name - full_path)/sizeof(char);

   path_type = Unknown;
   error_number = size = 0;
   creation_time.tv_sec = access_time.tv_sec = modification_time.tv_sec = 0;
   creation_time.tv_nsec = access_time.tv_nsec = modification_time.tv_nsec = 0;
}

// Destructor
PathInfo::~PathInfo()
{
   if (full_path != NULL) delete [] full_path;
   if (parent_path != NULL) delete [] parent_path;
   if (linked_path != NULL) delete [] linked_path;
   if (directory_stream != NULL) closedir(directory_stream);
}

// Store informations about the path; true if none error occurred
bool PathInfo::QueryInfo()
{
   sys_stat info;
   error_number = errno = 0;
   error_str = NULL;
   int status = lstat(full_path, &info);
   if (status)
   {
      error_number = errno;
      error_str = strerror(errno);
      printf("PathInfo::DoQuery @ lstat (%s): %s\n", full_path, error_str);
      fflush(stdout);
      return false;
   }
   creation_time = info.st_ctim;
   access_time = info.st_atim;
   modification_time = info.st_mtim;
   if (S_ISDIR(info.st_mode))
   {
      path_type = Directory;
      error_number = errno = 0;
      error_str = NULL;
      if ((directory_stream = opendir(full_path)) == NULL)
      {
         error_number = errno;
         error_str = strerror(errno);
         printf("PathInfo::DoQuery @ opendir (%s): %s\n", full_path, error_str);
         fflush(stdout);
         return false;
      }
      dirent *entry;
      do
      {
         if ((entry = readdir(directory_stream)) != NULL) ++size;
      } while (entry != NULL);
      size -= 2;
      if (errno)
      {
         error_number = errno;
         error_str = strerror(errno);
         printf("PathInfo::DoQuery @ readdir (%s): %s\n", full_path, error_str);
         fflush(stdout);
         return false;
      }
      error_number = errno = 0;
      error_str = NULL;
      if (closedir(directory_stream))
      {
         error_number = errno;
         error_str = strerror(errno);
         printf("PathInfo::DoQuery @ closedir (%s): %s\n", full_path, error_str);
         fflush(stdout);
         return false;
      }
      directory_stream = NULL;
   }
   else
   {
      bool link = false;

      size = info.st_size;
      do
      {
         switch(IFTODT(info.st_mode))
         {
            case DT_UNKNOWN: path_type  = Unknown;           break;
            case DT_BLK:     path_type  = BlockDevice;       break;
            case DT_CHR:     path_type  = CharacterDevice;   break;
            case DT_DIR:     path_type  = Directory;         break;
            case DT_FIFO:    path_type  = Pipe;              break;
            case DT_REG:     path_type  = RegularFile;       break;
            case DT_SOCK:    path_type  = Socket;            break;
            case DT_WHT:     path_type  = WhiteOut;          break;
            case DT_LNK:     path_type |= SymbolicLink;      break;
         }
         if (path_type & SymbolicLink)
         {
            link = true;
            error_number = errno = 0;
            error_str = NULL;
            int status = stat(full_path, &info);
            if (status)
            {
               error_number = errno;
               error_str = strerror(errno);
               printf("PathInfo::DoQuery @ stat (%s): %s\n", full_path, error_str);
               fflush(stdout);
               return false;
            }
         }
      } while (path_type & SymbolicLink);
      if (link)
      {
         path_type |= SymbolicLink;
         return get_linked_path();
      }
   }
   return true;
}

// Return the next file or folder contained by the directory, or NULL if: this is a file, the
// directory is empty or end of the stream is reached
PathInfo *PathInfo::Next()
{
   if (path_type != Directory) return NULL;
   if (directory_stream == NULL)
   {
      error_number = errno = 0;
      error_str = NULL;
      if ((directory_stream = opendir(full_path)) == NULL)
      {
         error_number = errno;
         error_str = strerror(errno);
         printf("PathInfo::Next @ opendir (%s): %s\n", full_path, error_str);
         fflush(stdout);
         return NULL;
      }
   }
   START_READ:
   error_number = errno = 0;
   error_str = NULL;
   dirent *entry = readdir(directory_stream);
   if (errno)
   {
      error_number = errno;
      error_str = strerror(errno);
      printf("PathInfo::Next @ readdir (%s): %s\n", full_path, error_str);
      fflush(stdout);
      return NULL;
   }
   else if (entry == NULL)
   {
      closedir(directory_stream);
      directory_stream = NULL;
      return NULL;
   }
   else
   {
      // skip current and parent directory entries
      if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) goto START_READ;

      int len = strlen(entry->d_name);
      char *entry_path = new char[len + path_len + 2];
      memset(entry_path, '\0', len + path_len + 2);
      strncpy(entry_path, full_path, path_len);
      if (full_path[path_len - 1] != '/') strcat(entry_path, "/");
      strncat(entry_path, entry->d_name, len);
      return new PathInfo(entry_path);
   }
}

// If DoQuery is false or Next is NULL, return the error occurred, otherwise return NULL
char *PathInfo::GetError()
{
   return error_str;
}

char *PathInfo::GetError(int *ErrorCode)
{
   *ErrorCode = error_number;
   return error_str;
}

// Getters
unsigned char PathInfo::GetType()
{
   return path_type;
}

char *PathInfo::GetPathName()
{
   return full_path;
}

char *PathInfo::GetParentName()
{
   if (parent_path == NULL)
   {
      int len = base_name_position ? base_name_position + 1 : base_name_position + 2;
      parent_path = new char[len];
      memset(parent_path, '\0', len);
      strncpy(parent_path, full_path, len - 1);
   }
   return parent_path;
}

char *PathInfo::GetBaseName()
{
   return &full_path[path_len > 1 ? base_name_position + 1 : 0];
}

char *PathInfo::GetLinkedPath()
{
   return linked_path;
}

int PathInfo::GetSize()
{
   return size;
}

char *PathInfo::GetCreationTime(char *Format)
{
   char *str = new char[27];
   memset(str, '\0', 27);
   tm *time = localtime(&creation_time.tv_sec);
   if (!strftime(str, 26, Format, time)) return NULL;
   return str;
}

char *PathInfo::GetAccessTime(char *Format)
{
   char *str = new char[27];
   memset(str, '\0', 27);
   tm *time = localtime(&access_time.tv_sec);
   if (!strftime(str, 26, Format, time)) return NULL;
   return str;
}

char *PathInfo::GetModificationTime(char *Format)
{
   char *str = new char[27];
   memset(str, '\0', 27);
   tm *time = localtime(&modification_time.tv_sec);
   if (!strftime(str, 26, Format, time)) return NULL;
   return str;
}

bool PathInfo::TypeIs(unsigned char FileType)
{
   return FileType & SymbolicLink ? path_type & FileType : path_type == FileType;
}

// Get the path of the file pointed by a symbolic link
bool PathInfo::get_linked_path()
{
   int len = size + 1;
   linked_path = new char[len + 1];

   memset(linked_path, '\0', len + 1);

   error_number = errno = 0;
   error_str = NULL;
   if (readlink(full_path, linked_path, len) == -1)
   {
      error_number = errno;
      error_str = strerror(errno);
      printf("PathInfo::get_linked_path @ readlink (%s): %s\n", full_path, error_str);
      fflush(stdout);
      return false;
   }
   return true;
}

bool PathInfoPtrNameComparator::operator()(PathInfo *Path1, PathInfo *Path2)
{
   return strcmp(Path1->GetBaseName(), Path2->GetBaseName()) < 0;
}
